﻿using FySystem.AssistantServices;
using FySystem.Converter;
using FySystem.IO;
using FySystem.Net.SimSocket;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FySystem.Net.LSTP
{
    public class LSTPConnection : LSTPBase
    {
        /// <summary>
        /// 传输文件时，每次传说1M
        /// </summary>
        const int FileBlock = 1024 * 1024;

        /// <summary>
        /// 存储所有发送的请求
        /// </summary>
        private Dictionary<string, RequestInfo> _requests = new Dictionary<string, RequestInfo>();
        private Dictionary<string, FileRequestInfo> _fileTransfer = new Dictionary<string, FileRequestInfo>();

        internal SimClient SimClient { get; }

        /// <summary>
        /// 自定义名称，用于标记连接功能
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 断线回调
        /// </summary>
        public Action<LSTPConnection> OnDisConnect { get; set; }

        /// <summary>
        /// 断线是否自动重连
        /// </summary>
        public bool IsAutoReconnect { get; set; } = true;

        /// <summary>
        /// 内部用
        /// </summary>
        /// <param name="client"></param>
        internal LSTPConnection(SimClient client)
        {
            SimClient = client;
        }

        /// <summary>
        /// 创建LSTP连接，同时连接服务器
        /// </summary>
        /// <param name="remoteIp"></param>
        /// <param name="remotePort"></param>
        /// <param name="onRequestCallback">收到请求回调</param>
        public LSTPConnection(IPAddress remoteIp, int remotePort, Action<LSTPRequest, LSTPWebResponse> onRequestCallback)
        {
            try
            {
                //初始化日志
                IAssistantServiceContext service = AssistantServiceContext.CreateService(30, false);
                service.Run();

                IP = remoteIp;
                Port = remotePort;
                OnRequestCallback = onRequestCallback;              //收到请求回调，由于是长连接，所以服务器也可以主动请求客户端

                SimClient = new SimClient(remoteIp, remotePort);
                SimClient.Tag = this;
                SimClient.OnReceive = OnReceive;
                SimClient.IsAutoReconnect = IsAutoReconnect;
                SimClient.OnDisConnect = simClient =>
                {
                    OnDisConnect?.Invoke(this);
                };
                SimClient.Connect();

                //启动垃圾回收线程
                Task task = new Task(GC);
                task.Start();
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 收到数据回调
        /// </summary>
        /// <param name="simClient"></param>
        /// <param name="frame"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void OnReceive(SimClient simClient, FrameInfo frame)
        {
            try
            {
                //收到的数据有两种可能：1.客户端向服务端的请求，2服务端请求客户端后，客户端的返回
                int position = 0;
                CSon cson = CSon.Parse(frame.Frame.AsSpan(), ref position, frame.Frame.Length);

                if(IsRequest(cson))
                {
                    //如果收到的是服务器发过来的request
                    LSTPRequest request = new LSTPRequest();
                    request.Connection = this;

                    //将收到的数据转换为request
                    DecodeRequest(request, cson);

                    //创建response并进行response回调
                    List<byte> bytes = null;
                    CreateResponseAndCallback(request, ref bytes);

                    //将response数据返回给远端
                    request.Connection.SimClient.Send(bytes);
                }
                else
                {
                    //如果收到的是服务器返回的response
                    LSTPResponse response = new LSTPResponse();
                    response.RequestId = cson["requestId"].BytesValue;
                    response.Status = cson["status"].IntValue;
                    response.Error = cson["error"].StringValue;
                    response.Body = cson["body"];

                    string strRequestId = NumberConversion.BytesToHexString(response.RequestId, 0, 16, false);
                    DistributorRequest(strRequestId, response);
                }
            }
            catch (Exception ex)
            {
                FyLogContext.Write(ex.Message);
            }
        }

        /// <summary>
        /// 异步调用远端请求
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public void BeginCall(LSTPWebRequest request,Action<LSTPResponse> callback)
        {
            try
            {
                if (request == null)
                    throw new Exception("传入请求为空~");

                //将请求信息转换为byte数组
                var cson = request.ToRequest();
                List<byte> data = null;
                cson.ToBytesList(ref data);

                //将请求的回调和request添加到字典
                string requestId = NumberConversion.BytesToHexString(request.RequestId, 0, 16, false);
                lock (_requests)
                {
                    _requests.Add(requestId, new RequestInfo()
                    {
                        Callback = callback
                    });
                }

                //发送数据
                SimClient.Send(data);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 发送文件，另一端接收的时候，如果要将文件重命名，则设置OnBeginReceiveFile回调，收到文件的第一包数据的时候会执行该回调，
        /// 然后在该回调中返回需要将文件保存的地址和重命名后的文件名
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="tag">用户自定义信息</param>
        /// <param name="finishCallback"></param>
        public void BeginSendFile(string fileName, string tag,Action finishCallback)
        {
            try
            {
                string fileId = string.Empty;
                LSTPWebRequest request = CreateFileTransferRequest(fileName, tag, ref fileId, 0, "");

                lock (_fileTransfer)
                {
                    _fileTransfer.Add(fileId, new FileRequestInfo()
                    {
                        Callback = finishCallback,
                        FullName = fileName,
                        ShortName = new FileInfo(fileName).Name
                    });
                }

                //将请求信息转换为byte数组
                var cson = request.ToRequest();
                List<byte> data = null;
                cson.ToBytesList(ref data);

                SimClient.Send(data);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 创建文件传输请求
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="pos"></param>
        /// <returns></returns>
        private LSTPWebRequest CreateFileTransferRequest(string fileName, string tag, ref string fileId, long pos, string dfileName)
        {
            var request = new LSTPWebRequest("/tranfileforinter");
            try
            {
                long totalLength = 0;           //文件总长度
                byte[] blockData = new byte[FileBlock];        //文件内容
                int blockLength = 0;                            //读取到了多少字节
                if (string.IsNullOrWhiteSpace(fileId))
                    fileId = Guid.NewGuid().ToString("N");

                using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
                {
                    totalLength = fs.Length;
                    fs.Position = pos;
                    blockLength = fs.Read(blockData, 0, FileBlock);
                }

                //头
                request.Head.Add("totals", totalLength);
                request.Head.Add("pos", pos);
                request.Head.Add("length", blockLength);
                request.Head.Add("fullname", fileName);
                request.Head.Add("filename", new FileInfo(fileName).Name);
                request.Head.Add("fileid", fileId);
                request.Head.Add("dfilename", dfileName);
                request.Head.Add("tag", tag == null ? "" : tag);

                //消息体
                CSon body = null;
                if (blockLength < FileBlock)
                    body = blockData.AsSpan().Slice(0, blockLength).ToArray();
                else
                    body = blockData;

                request.Body = body;
            }
            catch (Exception)
            {
                throw;
            }
            return request;
        }

        /// <summary>
        /// 查找返回的数据是哪个请求的，然后调用回调函数
        /// </summary>
        /// <param name="strRequestId"></param>
        /// <param name="response"></param>
        internal void DistributorRequest(string strRequestId, LSTPResponse response)
        {
            try
            {
                Action<LSTPResponse> callback = null;
                lock (_requests)
                {
                    if (_requests.ContainsKey(strRequestId))
                    {
                        callback = _requests[strRequestId].Callback;
                        _requests.Remove(strRequestId);
                    }
                }

                if (callback != null)
                {
                    //如果是常规请求的返回
                    callback?.Invoke(response);
                }
                else
                {
                    //如果为空，则可能是文件传输返回的
                    if (response.Body == null)
                        return;
                    if (response.Body.IsCSonNull)
                        return;

                    if (response.Body.IsContainKey("fileid"))
                    {
                        //该字段存在，说明是文件传输的返回
                        long totals = response.Body["totals"].LongValue.Value;
                        long pos = response.Body["pos"].LongValue.Value;
                        string fileId = response.Body["fileid"].StringValue;
                        string dfileName = response.Body["dfilename"].StringValue;
                        string tag = response.Body["tag"].StringValue;

                        FileRequestInfo fileRequestInfo = null;
                        lock (_fileTransfer)
                        {
                            if (_fileTransfer.ContainsKey(fileId))
                            {
                                fileRequestInfo = _fileTransfer[fileId];
                            }
                        }

                        if (fileRequestInfo != null)
                        {
                            LSTPWebRequest request = null;

                            if (response.Status == 0)
                            {
                                //如果上一包传输成功
                                if (pos >= totals - 1)
                                {
                                    //说明文件已经传输结束
                                    fileRequestInfo.Callback?.Invoke();
                                    lock (_fileTransfer)
                                        _fileTransfer.Remove(fileId);
                                }

                                pos += FileBlock;
                                request = CreateFileTransferRequest(fileRequestInfo.FullName, tag, ref fileId, pos, dfileName);
                            }
                            else
                            {
                                //如果上一包传输失败，重新发送，并将失败次数加1
                                fileRequestInfo.ErrorCount++;
                                if (fileRequestInfo.ErrorCount > 5)
                                {
                                    //这里应该要有一个抛错的，目前先不做，正常来说不会报错，除非一方关闭程序
                                }
                                else
                                {
                                    request = CreateFileTransferRequest(fileRequestInfo.FullName, tag, ref fileId, pos, dfileName);
                                }
                            }

                            if (request != null)
                            {
                                //将请求信息转换为byte数组
                                var cson = request.ToRequest();
                                List<byte> data = null;
                                cson.ToBytesList(ref data);

                                SimClient.Send(data);
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 垃圾回收
        /// </summary>
        private void GC()
        {
            while (true)
            {
                Thread.Sleep(60000);
                DateTime now = DateTime.Now;
                try
                {
                    List<string> invalidates = new List<string>();

                    lock (_requests)
                    {
                        foreach(var key in _requests.Keys)
                        {
                            if((now - _requests[key].RequestTime).TotalMinutes>5)
                                invalidates.Add(key);
                        }

                        if(invalidates.Count>0)
                        {
                            foreach (var key in invalidates)
                                _requests.Remove(key);
                        }
                    }

                    invalidates.Clear();

                    lock (_fileTransfer)
                    {
                        foreach(var key in _fileTransfer.Keys)
                        {
                            if ((now - _fileTransfer[key].RequestTime).TotalMinutes > 60)
                                invalidates.Add(key);
                        }

                        if (invalidates.Count > 0)
                        {
                            foreach (var key in invalidates)
                                _fileTransfer.Remove(key);
                        }
                    }
                }
                catch { }
            }
        }
    }
}
